package com.novunda.platform.security;

import com.novunda.platform.common.context.SpringContextHolder;
import com.novunda.platform.common.utils.StringUtil;
import com.novunda.platform.entities.User;
import com.novunda.platform.services.PlatformService;
import com.novunda.tca.tdk.utils.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author linfeng
 * @since 2016/9/28.
 */
public abstract class CommonAuthenticationFilter extends AuthenticatingFilter {

    private static final Logger log = LoggerFactory.getLogger(CommonAuthenticationFilter.class);

    protected String failureKeyAttribute = "shiroLoginFailure";

    private PlatformService platformService;

    @Override
    public void setLoginUrl(String loginUrl) {
        String previous = getLoginUrl();
        if (previous != null) {
            appliedPaths.remove(previous);
        }

        super.setLoginUrl(loginUrl);
        if (log.isTraceEnabled()) {
            log.trace("Adding login url to applied paths.");
        }

        appliedPaths.put(getLoginUrl(), null);

    }

    /**
     * 用户登出
     *
     * @param request  ServletRequest
     * @param response ServletResponse
     */
    protected void logout(ServletRequest request, ServletResponse response) {
        Subject subject = getSubject(request, response);
        try {
            subject.logout();
        } catch (SessionException e) {
            if (log.isWarnEnabled()) {
                log.warn(e.getMessage(), e);
            }
        }
    }

    /**
     * 判断不是ajax请求
     *
     * @param request ServletRequest
     * @return boolean
     */
    protected boolean isAjaxRequest(ServletRequest request) {
        HttpServletRequest servletRequest = (HttpServletRequest) request;

        return "XMLHttpRequest".equalsIgnoreCase(servletRequest.getHeader("X-Requested-With"))
                || request.getParameter("ajax") != null;
    }

    public String getFailureKeyAttribute() {
        return failureKeyAttribute;
    }


    public void setFailureKeyAttribute(String failureKeyAttribute) {
        this.failureKeyAttribute = failureKeyAttribute;
    }

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return request instanceof HttpServletRequest && WebUtils.toHttp(request).getMethod().equalsIgnoreCase("POST");
    }

    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        String className = ae.getClass().getName();
        request.setAttribute(getFailureKeyAttribute(), className);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

        if (isLoginRequest(request, response)) {
            return false;
        } else {
            return super.isAccessAllowed(request, response, mappedValue);
        }
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }

                return executeLogin(request, response);
            } else {

                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }

                if (isAjaxRequest(request)) {

                    String message = "<script language='javascript'>"
                            + "window.top.location.href='"
                            + request.getServletContext().getContextPath() + getLoginUrl()
                            + "';</script>";
                    write(response, message);

                    return false;

                } else {

                    return true;
                }

            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [{}]", getLoginUrl());
            }

            if (isAjaxRequest(request)) {

                String message = "<script language='javascript'>"
                        + "window.top.location.href='"
                        + request.getServletContext().getContextPath() + getLoginUrl()
                        + "';</script>";
                write(response, message);
            } else {

                saveRequestAndRedirectToLogin(request, response);
            }

            return false;
        }
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String principal = (String) token.getPrincipal();
        User user = getPlatformService().getUserByLoginName(principal);
        // 更新登录IP和时间
        getPlatformService().updateUserLoginInfo(user.getId());
        getPlatformService().loginLog();


        if (isAjaxRequest(request)) {

            String message = "{success:true,message:'登录成功'}";
            write(response, message);

        } else {

            String requestUrl = null;
            SavedRequest savedRequest = WebUtils.getSavedRequest(request);
            if (savedRequest != null) {
                requestUrl = savedRequest.getRequestUrl();
            }

            if (StringUtils.isBlank(requestUrl)) {
                WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
            } else {
                WebUtils.redirectToSavedRequest(request, response, requestUrl);
            }
        }

        return false;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request,
                                     ServletResponse response) {

        if (log.isDebugEnabled()) {
            log.debug("Authentication exception", e);
        }


        String error = null;
        if (e instanceof ShiroException) {
            error = "用户名或密码错误";

        } else if (e instanceof Exception) {
            error = "登陆内部错误";
        }


        if (isAjaxRequest(request)) {
            String message = "{success:false, message:'登录失败', error:" + error + "}";
            write(response, message);
            return true;
        }
        if (StringUtils.isNotBlank(error)) {
            throw new LoginException(error, e);
        }

        setFailureAttribute(request, e);

        return true;
    }

    /**
     * 获取系统业务对象
     */
    protected PlatformService getPlatformService() {
        if (platformService == null) {
            platformService = SpringContextHolder.getBean(PlatformService.class);
        }
        return platformService;
    }

    /**
     * 输出信息到页面
     *
     * @param response ServletResponse
     * @param result   输出内容
     */
    protected void write(ServletResponse response, String result) {
        PrintWriter writer = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("text/html;charset=UTF-8");
            writer = response.getWriter();
            writer.write(result);
        } catch (IOException e) {
            if (log.isErrorEnabled()) {
                log.error(e.getMessage(), e);
            }
        } finally {
            if (writer != null) {
                writer.flush();
                writer.close();
            }
        }
    }

}
